"""
  Copyright (c) 2024 Northeastern University (China)
  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
"""
# 任务：使用ccscsner扫描源码项目得到依赖项
# 1.依赖项去除重复，只记录名字，scanner_dep.json

import math
import os
import json
import sys
import threading

from tqdm import tqdm

from ccscanner.utils.File import File
from src.common_utils.utils_file import read_jl_list, read_jl

# 当前文件的路径
g_cur_dir = os.path.dirname(os.path.abspath(__file__))
# 当前文件的父路径
father_path = os.path.dirname(g_cur_dir)
sys.path.append(father_path)
import argparse
from src.common_utils import configure
from ccscanner.utils.utils import read_txt, save_js, read_js
from ccscanner.extractors.conan_extractor import ConanExtractor
from ccscanner.extractors.control_extractor import ControlExtractor
from ccscanner.extractors.cmake_extractor import CmakeExtractor
from ccscanner.extractors.autoconf_extractor import AutoconfExtractor
from ccscanner.extractors.submodule_extractor import SubmodExtractor
from ccscanner.extractors.vcpkg_extractor import VcpkgExtractor
from ccscanner.extractors.pkg_extractor import PkgExtractor
from ccscanner.extractors.meson_extractor import MesonExtractor
from ccscanner.extractors.clib_extractor import ClibExtractor
from ccscanner.extractors.bazel_extractor import BazelExtractor
from ccscanner.extractors.ms_extractor import MsExtractor
from ccscanner.extractors.xmake_extractor import XmakeExtractor
from ccscanner.extractors.make_extractor import MakeExtractor
from concurrent.futures import ThreadPoolExecutor
# from ccscanner.extractors.buckaroo_extractor import BuckarooExtractor
from ccscanner.extractors.dds_extractor import DdsExtractor
# from ccscanner.extractors.buck_extractor import BuckExtractor
from ccscanner.extractors.build2_extractor import Build2Extractor
scanner_dep_path=os.path.join(configure.CONFIG['ossdb_construct'],'scaner_dep.jl')

parser = argparse.ArgumentParser()
parser.add_argument('-d', type=str, default='',
        help='set directory to scan')
parser.add_argument('-t', type=str, default='results.json',
        help='save results to file')

CONF_FILES = ['configure', 'configure.in', 'configure.ac']
from src.common_utils.logger import logger


class scanner(object):
    def __init__(self, dir_target) -> None:
        self.target = dir_target
        self.extractors = []
        self.scan()

    def scan(self):
        for root, dirs, filenames in os.walk(self.target):
            for filename in filenames:
                extractor = None
                filename_lower = filename.lower()
                ## TODO: readme module
                # if filename_lower.startswith('readme'):
                #     extractor = ReadmeExtractor
                #     arg = os.path.join(root, filename)
                if filename_lower == 'control' or filename_lower.endswith('.dsc'):
                    extractor = ControlExtractor
                    arg = os.path.join(root, filename)
                elif filename == 'CMakeLists.txt' or filename.endswith('.cmake'):
                    extractor = CmakeExtractor
                    arg = os.path.join(root, filename)
                elif filename_lower in CONF_FILES:
                    extractor = AutoconfExtractor
                    arg = os.path.join(root, filename)
                elif filename == '.gitmodules':
                    extractor = SubmodExtractor
                    arg = root
                elif filename == 'vcpkg.json':
                    extractor = VcpkgExtractor
                    arg = os.path.join(root, filename)
                elif filename in ['conanfile.txt', 'conaninfo.txt', 'conanfile.py']:
                    extractor = ConanExtractor
                    arg = os.path.join(root, filename)
                elif filename.endswith('.pc'):
                    extractor = PkgExtractor
                    arg = os.path.join(root, filename)
                elif filename == 'meson.build':
                    extractor = MesonExtractor
                    arg = os.path.join(root, filename)
                elif filename in ['package.json', 'clib.json']:
                    extractor = ClibExtractor
                    arg = os.path.join(root, filename)
                elif filename == 'package.json5':
                    extractor = DdsExtractor
                    arg = os.path.join(root, filename)
                elif filename in ['bazel.build', 'BUILD']:
                    extractor = BazelExtractor
                    arg = os.path.join(root, filename)
                elif filename.endswith(('.vcxproj', '.vbproj', '.props')):
                    extractor = MsExtractor
                    arg = os.path.join(root, filename)
                elif filename == 'xmake.lua':
                    extractor = XmakeExtractor
                    arg = os.path.join(root, filename)
                ## elif filename in ['buckaroo.toml', 'buckaroo.lock.toml', '.buckconfig']:
                # elif filename in 'buckaroo.toml':
                #     extractor = BuckarooExtractor
                #     arg = os.path.join(root, filename)
                # elif filename == 'BUCK':
                #     extractor = BuckExtractor
                #     arg = os.path.join(root, filename)
                elif filename.lower().startswith('makefile'):
                    extractor = MakeExtractor
                    arg = os.path.join(root, filename)
                elif filename.lower() == 'manifest':
                    file_path = os.path.join(root, filename)
                    context = read_txt(file_path)
                    if 'build2' not in context:
                        continue
                    extractor = Build2Extractor
                    arg = file_path

                if extractor is None:
                    continue
                try:
                    extractor = extractor(arg)
                    extractor.run_extractor()
                    self.extractors.append(extractor.to_dict())
                except Exception as e:
                    logger.error('[+]  An error occurred', exc_info=True)
                    logger.error(e)

    def to_dict(self):
        # dumps 将数据生成字符串
        # loads 将序列化的json字符串, 载入为JSON对象
        return json.loads(json.dumps(self, default=lambda o: o.__dict__))


def batch_scan(wright_path:str=None,result_path:str=None,projects:list=None,idx:int=0):
    """
    批量项目扫描,直接将扫描结果取name,合成一个json，
    并且单独为每个项目生成依赖项文件，为了测试方便
    :return:
    """
    logger.info(f"[+] detector by ccscaner")
    if projects is None:
        target_path = configure.CONFIG['harmony_src']
        projects = []
        for project in os.listdir(target_path):
            projects.append(''.join([target_path, os.sep, project]))
    if wright_path is None:
        wright_path=configure.CONFIG['all_dep']
    if result_path is None:
        result_path = result_path=g_cur_dir+r'\data_out\scaner_dep.jl'
    all_deps=[]
    for project in tqdm(projects, total=len(projects), desc=f"thread {idx} repo扫描进度", unit="个repo"):
        scanner_obj = scanner(project)
        res = scanner_obj.to_dict()
        inputRepo = project.split('\\')[-1]
        if wright_path !="false":
            write_path = wright_path + r"\\" + "ccscaner_"+inputRepo + ".json"
            File.write_json(self=None, dict_list=res, path=write_path)
        deps = generate_dir_deps(res)
        for dep in deps:
            name = dep.get("name")
            dep={
                'name':name
            }
            if dep not in all_deps:
                all_deps.append(dep)
    save_js(all_deps, result_path)
    print(f"线程{idx}的结果:{len(all_deps)}")

def generate_dir_deps(result):
    """
    将生成的结果集转化为列表形式的结果，便于前端展示
    :param result:
    :return:
    """
    dir_deps = []
    extractors = result.get("extractors")
    for extractor in extractors:
        deps = extractor.get("deps")
        for dep in deps:
            depname = dep.get("depname")
            version = dep.get("version")
            dir_dep = {
                "name": depname,
                "version": version,
            }
            dir_deps.append(dir_dep)
    return dir_deps

def single_scan(target_path,result_path):
    """
    给定存放源码项目的路径，逐个扫描，单独为每个文件生成依赖项记录，不合并结果
    :return:
    """
    path_list = next(os.walk(target_path))[1]
    print(path_list)
    for p in path_list:
        project_path=os.path.join(target_path, p)
        print(project_path)
        write_path =result_path+r"\\"+p+"_ccscaner"
        scanner_obj = scanner(project_path)
        res = scanner_obj.to_dict()
        File.write_json(self=None, dict_list=res, path=write_path)

def split_list(list, n):
    """
    将数据n等分，分发给n个线程执行

    :param list: 原始完整list
    :param n: 切分数量，即线程数量
    :return: 切分后的list起始位置
    """
    step_count = math.ceil(len(list) / n)
    for i in range(0, len(list), step_count):
        end = i + step_count
        if end > len(list):
            end = len(list)
        # print(f"yield: {i}:{end}")
        yield list[i:end]
def main_multi(projects,num):
    open(scanner_dep_path, 'w').close()
    print(f"共要处理{len(projects)}个仓库")
    _idx = 0
    t = []
    for list_arr in split_list(projects, num):  # 启动多线程下载
        print(f"thread:{_idx + 1} do:{len(list_arr)}")
        t.append(threading.Thread(target=batch_scan, args=('false',scanner_dep_path,list_arr,_idx + 1)))
        _idx += 1
    for i in t:
        i.start()
    for j in t:
        j.join()  # 为了等待所有子线程结束后再执行主线程的代码


if __name__ == '__main__':
    allprojects = read_jl(
        r"E:\open-harmony\ossdb_construct\cloned_projects_paths.jl")
    main_multi(projects=allprojects,num=4)
    #single_scan(target_path=configure.CONFIG['harmony_src'],result_path=configure.CONFIG['harmony_dep'])




    